#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <stddef.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <queue>

#include "config.h"
#include "event_queue.h"
#include "inotify.h"
#include "file.h"

using namespace std;
#define MAX_PATH_LEN 1024
typedef enum {OPT_CREATE, OPT_DELETE, OPT_MODIFY, OPT_MOVED_TO} OPT;

Inotify::Inotify(Config *conf){
    this->keep_running = 1;
    this->conf = conf;
    this->inotify_fd = this->open_inotify_fd();
    printf("intofi_fd: %d\n", this->inotify_fd);
    this->q = queue_create();
}

int Inotify::open_inotify_fd(){
    int fd;
    this->watched_items = 0;
    
    fd = inotify_init();
    if(fd < 0){
        perror("inotify_init () = ");
    }
    return fd;
}

void Inotify::set_mask(unsigned long mask){
    this->mask = mask;
}

void Inotify::insert_watch_dir_map(int wd, const char *dirname){
    std::string dir  = dirname;    
    this->watch_dir_map.insert(std::map<int, string>::value_type(wd, dir));
}

std::string Inotify::get_wd_path(int wd){ 
    std::string wd_path;
    std::map<int, string>::iterator itr = this->watch_dir_map.begin();
    itr = this->watch_dir_map.find(wd);
    if( itr != this->watch_dir_map.end() ){        
        wd_path = itr->second;
    }else{
        wd_path = "";
    }
    return wd_path;          
}

std::string Inotify::get_event_path(int wd, const char *cur_name){   
    std::string path;
    path = get_wd_path(wd);
    path += "/";
    path += cur_name;
    return path;
}

int Inotify::watch_dir(int fd, const char *dirname, unsigned long mask){
    int wd;
    wd = inotify_add_watch(fd, dirname, mask);
    
    if(wd < 0){
        printf("Cannot add watch for \"%s\" with event mask %lX", dirname,
                mask);
        fflush(stdout);
        perror(" ");
    } else{
        watched_items++;
        if(this->conf->is_debug){
            printf("Watching %s WD=%d\n", dirname, wd);
            printf("Have watched %d items\n", watched_items);
        }
        this->insert_watch_dir_map(wd, dirname);
    }
    return wd;    
}

void Inotify::recur_watch_dir(int fd, const char *dirname, unsigned long mask){           
    DIR *dir;    
    dir = opendir(dirname);
    struct dirent *ptr;
    
    this->watch_dir(fd, dirname, mask);
    
    if(NULL != dir){
        char sub_path[MAX_PATH_LEN];        
        while( (ptr = readdir(dir)) != NULL ){
            if( strncmp(ptr->d_name, ".", 1) == 0 ){
                continue;
            }
            memset(sub_path, '\0', sizeof(sub_path));
            if( strlen(dirname) + 1 + strlen(ptr->d_name) + 1 > MAX_PATH_LEN ){
                printf("sub file path is to long. %s/%s", dirname, ptr->d_name);
                continue;
            }            
            snprintf(sub_path, sizeof(sub_path) - 1, "%s/%s", dirname, ptr->d_name);
            if( is_dir(sub_path) ){                
                recur_watch_dir(fd, sub_path, mask);
            }
        }
    }
    closedir(dir);
}

int Inotify::ignore_wd(int fd, int wd){
    int r;
    r = inotify_rm_watch(fd, wd);
    if(r < 0){
        perror("inotify_rm_watch(fd, wd) = ");
    }else{
        watched_items--;
    }
    return r;    
}

int Inotify::send_data_to_pipe(const char *operate, const char *path){
    std::string snd_str = "";
    snd_str = operate;
    snd_str += "\t";
    snd_str += path;
    snd_str += "\n";
    printf("pipefd: %d snd_str: %s size: %d  keep_running: %d watched_items: %d\n", this->conf->pipefd[0], snd_str.c_str(), (int)snd_str.size(), this->keep_running , this->watched_items);
    int ret = send(this->conf->pipefd[0], snd_str.c_str(), snd_str.size(), 0);        
    printf("send pipe ret: %d\n", ret);
    return 0;    
}

int Inotify::event_check(int fd){
    fd_set rfds;
    FD_ZERO(&rfds);
    FD_SET(fd, &rfds);
    return select(FD_SETSIZE, &rfds, NULL, NULL, NULL);//FD_SETSIZE 会受系统默认值的影响
}

int Inotify::read_events(queue_t q, int fd) {
    char buffer[16384];
    size_t buffer_i;
    struct inotify_event *pevent;
    queue_entry_t event;
    ssize_t r;
    size_t event_size, q_event_size;
    int count = 0;
    
    memset(buffer, '\0', sizeof(buffer));
    r = read(fd, buffer, sizeof(buffer) - 1);
    if(r < 0){
        return r;
    }
    buffer_i = 0;
    while(buffer_i < (size_t)r){
        pevent =  (struct inotify_event *)&buffer[buffer_i];
        event_size = offsetof(struct inotify_event, name) + pevent->len;
        q_event_size = offsetof(struct queue_entry, inot_ev.name) + pevent->len;
        event = (queue_entry_t)malloc(q_event_size);
        memmove(&(event->inot_ev), pevent, event_size);
        queue_enqueue(event, q);
        buffer_i += event_size;
        count++;
    }
    if(this->conf->is_debug){
        printf("\n%d events queued\n", count);
    }
    return count;    
}

void sync_event_add(int wd, char *file_name, char *opt) {
    if( strncmp(file_name, ".", 1) == 0 ){
        return;
    }else if( file_name[strlen(file_name) - 1] == '~' ){
        return;
    }else if( strstr(file_name, ".new") ){
        char *ptr = strrchr(file_name, '.'); 
        if( strcmp(ptr, ".new") == 0 ){
            return;
        }        
    }   
}

void Inotify::handle_events(queue_t q) {
    queue_entry_t event;
    while(!queue_empty(q)){
        printf("event queue len = %d\n", q->len);
        event = queue_dequeue(q);
        handle_event(event);
        free(event);
    }    
}

void Inotify::handle_event(queue_entry_t event){
    const char *cur_event_filename = NULL;
    const char *cur_event_file_or_dir = NULL;
    std::string ev_path;
    
    int cur_event_wd = event->inot_ev.wd;
    int cur_event_cookie = event->inot_ev.cookie;
    unsigned long flags;
    
    if(event->inot_ev.len){
        cur_event_filename = event->inot_ev.name;
    }
    if(event->inot_ev.mask & IN_ISDIR){
        cur_event_file_or_dir = "Dir";
    }else{
        cur_event_file_or_dir = "File";
    }
    
    ev_path = this->get_event_path(cur_event_wd, cur_event_filename);
    
    flags = event->inot_ev.mask & 
            ~(IN_ALL_EVENTS | IN_UNMOUNT | IN_Q_OVERFLOW | IN_IGNORED);
    
    switch( event->inot_ev.mask &
            (IN_ALL_EVENTS | IN_UNMOUNT | IN_Q_OVERFLOW | IN_IGNORED) ){
        /* File was accessed */
        case IN_ACCESS:
            printf("ACCESS: %s \"%s\" on WD #%i\n",
                    cur_event_file_or_dir, cur_event_filename, cur_event_wd);
            break;
            /* File was modified */
        case IN_MODIFY:
            printf("MODIFY: %s \"%s\" on WD #%i\n",
                    cur_event_file_or_dir, cur_event_filename, cur_event_wd);                        
            this->send_data_to_pipe("modify", this->get_event_path(cur_event_wd, cur_event_filename).c_str());
            break;
            /* File changed attributes */
        case IN_ATTRIB:
            printf("ATTRIB: %s \"%s\" on WD #%i\n",
                    cur_event_file_or_dir, cur_event_filename, cur_event_wd);
            break;
            /* File open for writing was closed */
        case IN_CLOSE_WRITE:
            printf("CLOSE_WRITE: %s \"%s\" on WD #%i\n",
                    cur_event_file_or_dir, cur_event_filename, cur_event_wd);
            break;
            /* File open read-only was closed */
        case IN_CLOSE_NOWRITE:
            printf("CLOSE_NOWRITE: %s \"%s\" on WD #%i\n",
                    cur_event_file_or_dir, cur_event_filename, cur_event_wd);
            break;

            /* File was opened */
        case IN_OPEN:
            printf("OPEN: %s \"%s\" on WD #%i\n",
                    cur_event_file_or_dir, cur_event_filename, cur_event_wd);
            break;
            /* File was moved from X */
        case IN_MOVED_FROM:
            printf("MOVED_FROM: %s \"%s\" on WD #%i. Cookie=%d\n",
                    cur_event_file_or_dir, cur_event_filename, cur_event_wd,
                    cur_event_cookie);
            break;
            //this->send_data_to_pipe("moved_from", this->get_event_path(cur_event_wd, cur_event_filename).c_str());
            /* File was moved to X */
        case IN_MOVED_TO:
            printf("MOVED_TO: %s \"%s\" on WD #%i. Cookie=%d\n",
                    cur_event_file_or_dir, cur_event_filename, cur_event_wd,
                    cur_event_cookie);           
            //sync_event_add(cur_event_wd, cur_event_filename, "update"); 
            this->send_data_to_pipe("moved_to", this->get_event_path(cur_event_wd, cur_event_filename).c_str());
            break;
            /* Subdir or file was deleted */
        case IN_DELETE:
            printf("DELETE: %s \"%s\" on WD #%i\n",
                    cur_event_file_or_dir, cur_event_filename, cur_event_wd);
            //sync_event_add(cur_event_wd, cur_event_filename, "delete");
            this->send_data_to_pipe("delete", this->get_event_path(cur_event_wd, cur_event_filename).c_str());
            break;
            /* Subdir or file was created */
        case IN_CREATE:
            printf("CREATE: %s \"%s\" on WD #%i\n",
                    cur_event_file_or_dir, cur_event_filename, cur_event_wd);                
            if( is_dir(ev_path.c_str()) ){
                this->watch_dir(this->inotify_fd, ev_path.c_str(), this->mask);
            }
            this->send_data_to_pipe("create", ev_path.c_str());                       
            break;
            /* Watched entry was deleted */
        case IN_DELETE_SELF:
            printf("DELETE_SELF: %s \"%s\" on WD #%i\n",
                    cur_event_file_or_dir, cur_event_filename, cur_event_wd);
            break;

            /* Watched entry was moved */
        case IN_MOVE_SELF:
            printf("MOVE_SELF: %s \"%s\" on WD #%i\n",
                    cur_event_file_or_dir, cur_event_filename, cur_event_wd);
            break;

            /* Backing FS was unmounted */
        case IN_UNMOUNT:
            printf("UNMOUNT: %s \"%s\" on WD #%i\n",
                    cur_event_file_or_dir, cur_event_filename, cur_event_wd);
            break;

            /* Too many FS events were received without reading them
               some event notifications were potentially lost.  */
        case IN_Q_OVERFLOW:
            printf("Warning: AN OVERFLOW EVENT OCCURRED: \n");
            break;

            /* Watch was removed explicitly by inotify_rm_watch or automatically
               because file was deleted, or file system was unmounted.  */
        case IN_IGNORED:
            watched_items--;
            printf("IGNORED: WD #%d\n", cur_event_wd);
            printf("Watching = %d items\n", watched_items);
            break;

            /* Some unknown message received */
        default:
            printf("UNKNOWN EVENT \"%X\" OCCURRED for file \"%s\" on WD #%i\n",
                    event->inot_ev.mask, cur_event_filename, cur_event_wd);
            break;
    }
    /* If any flags were set other than IN_ISDIR, report the flags */
    if (flags & (~IN_ISDIR)) {
        flags = event->inot_ev.mask;
        printf("Flags=%lX\n", flags);
    }    
}

int Inotify::process_inotify_events(queue_t q, int fd){
    printf("process_inotify_events\n");    
    while( this->keep_running && (this->watched_items > 0) ){                
        if(event_check(fd) > 0){            
            int r;
            r = read_events(q, fd);
            if(r < 0){
                break;
            }else{
                handle_events(q);                 
            }
        }        
    }
    return 0;
}

int Inotify::close_inotify_fd(int fd){
    int r;
    if( (r = close(fd)) < 0 ){
        perror("close (fd) = ");
    }
    
    watched_items = 0;
    return r;
}

Inotify::~Inotify(){        
    free(q);    
    watch_dir_map.clear();    
}